import * as log4js from "log4js";
import {Logger} from "log4js";
import ConstUtils from "./const-utils";
import {AsyncLocalStorage} from "async_hooks";
import {REDIS} from "../configs/config";
import DateUtils from "./date-utils";

const asyncLocalStorage = new AsyncLocalStorage();
const constUtils = new ConstUtils();
const dateUtils = new DateUtils();

// 上下文携带参数
declare interface ContextParams {
    userId?: string;
    battleId?: string;
    roomId?: string;
}

/**
 * 日志工具类
 * @see https://log4js-node.github.io/log4js-node/
 */
class LogUtils {
    // 日志管理对象
    private loggerMap = new Map();

    constructor() {
        this.loadProperties();
    }

    /**
     * 加载日志的配置文件
     */
    private loadProperties = () => {
        // 自定义格式化日志
        log4js.addLayout('json', function (config) {
            return function (logEvent) {
                const logDesc = {
                    ...logEvent,
                    // 覆盖以下属性
                    startTime: dateUtils.formatDate(logEvent.startTime),
                    level: logEvent.level.levelStr,
                    callStack: null,
                    functionName: null,
                }
                return JSON.stringify(logDesc) + config.separator;
            }
        });

        // 配置初始化
        log4js.configure({
            appenders: {
                // 记录到redis
                // @see https://github.com/log4js-node/redis
                redis: {
                    type: '@log4js-node/redis',
                    host: REDIS.HOST,
                    port: REDIS.PORT,
                    pass: REDIS.PASSWORD,
                    // 发布的redis渠道
                    channel: 'chess-log-channel',
                    layout: {
                        type: 'json',
                        separator: ',',
                    },
                },
                // 带日期的文件
                dateFile: {
                    type: "dateFile",
                    filename: "./logs/five-chess-server.log",
                    pattern: ".yyyy-MM-dd",
                    layout: {
                        type: 'pattern',
                        pattern: '%d{yyyy-MM-dd hh:mm:ss.SSS}|%p|%X{traceId}|%X{class}|%X{func}|%m'
                    },
                },
                // 输出到文件
                file: {
                    type: "file",
                    filename: "./logs/five-chess-server.log",
                    layout: {
                        type: 'pattern',
                        pattern: '%d{yyyy-MM-dd hh:mm:ss.SSS}|%p|%X{traceId}|%X{class}|%X{func}|%m'
                    },
                },
                // 输出到控制台
                console: {
                    type: "console",
                    layout: {
                        type: 'pattern',
                        pattern: '%d{yyyy-MM-dd hh:mm:ss.SSS}|%p|%X{traceId}|%X{class}|%X{func}|%f{1}:%l|%m'
                    },
                },
            },
            categories: {
                default: {appenders: ["console", 'file', 'dateFile', 'redis'], level: "debug", enableCallStack: true},
            }
        });
    }

    /**
     * 创建trace追踪编号
     * @param token 会话token
     */
    createTraceId = (token?: string) => {
        // 创建追踪Id
        const traceId = constUtils.getUUId();
        // 追踪id记录到上下文
        asyncLocalStorage.enterWith({traceId, token, category: '五子棋'});
    }

    /**
     * 创建上下文信息
     * @param clsName
     * @param func
     * @param opts
     */
    createContext = (clsName: string, func: string, opts?: ContextParams): Logger => {
        const store: any = asyncLocalStorage.getStore();
        const traceId: string = store.traceId;
        const token: string = store.token;
        const category: string = store.category;
        // 方法级别的日志对象
        const key = `${traceId}.${clsName}.${func}`;
        // 获取上下文日志对象
        let logger = this.loggerMap.get(key);
        if (!logger) {
            // 否则创建此key下的日志对象
            logger = log4js.getLogger(category);
            logger.addContext('traceId', traceId);
            logger.addContext('token', token);
            logger.addContext('class', clsName);
            logger.addContext('func', func);
            logger.addContext('userId', opts?.userId);
            logger.addContext('battleId', opts?.battleId);
            logger.addContext('roomId', opts?.roomId);
            this.loggerMap.set(key, logger);
        }
        return logger;
    }
}

export default LogUtils;